<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
</body>
</html>
<script>
    // Thunk函数
    // Thunk函数是自动执行Generator函数的一种方法

    // 参数的求值策略
    // Thunk 函数早在上个世纪 60 年代就诞生了
    // 那时 编程语言刚刚起步 计算机学家还在研究 编译器怎么写比较好 一个争论的焦点是"求值策略" 即函数的参数到底应该何时求值

    var x = 1;

    function f(m) {
        return m * 2;
    }

    f(x + 5)

    // 上面代码先定义函数f 然后向它传入表达式x + 5 请问，这个表达式应该何时求值
    // 一种意见是"传值调用"（call by value） 即在进入函数体之前 就计算x + 5的值（等于 6）再将这个值传入函数f C 语言就采用这种策略
    f(x + 5)
    // 传值调用时，等同于
    f(6)
    // 另一种意见是“传名调用”（call by name）即直接将表达式x + 5传入函数体 只在用到它的时候求值 Haskell 语言采用这种策略
    f(x + 5)
    // 传名调用时，等同于
    (x + 5) * 2


    // Thunk函数的含义
    // 编译器的“传名调用”实现 往往是将参数放到一个临时函数之中 再将这个临时函数传入函数体 这个临时函数就叫做 Thunk 函数
    function f(m) {
        return m * 2;
    }

    f(x + 5);

    // 等同于

    var thunk = function () {
        return x + 5;
    };

    function f(thunk) {
        return thunk() * 2;
    }

    // 上面代码中 函数 f 的参数x + 5被一个函数替换了 凡是用到原参数的地方 对Thunk函数求值即可
    // 这就是 Thunk 函数的定义 它是“传名调用”的一种实现策略 用来替换某个表达式

    // JavaScript语言的Thunk函数
    // JavaScript语言是传值调用 它的Thunk函数含义有所不同
    // 在JavaScript语言中 Thunk函数替换的不是表达式 而是多参数函数 将其替换成一个只接受回调函数作为参数的单参数函数

    // 正常版本的readFile（多参数版本）
    fs.readFile(fileName, callback);

    // Thunk版本的readFile（单参数版本）
    var Thunk = function (fileName) {
        return function (callback) {
            return fs.readFile(fileName, callback);
        };
    };

    var readFileThunk = Thunk(fileName);
    readFileThunk(callback);

    // 上面代码中 fs模块的readFile方法是一个多参数函数 两个参数分别为文件名和回调函数
    // 经过转换器处理 它变成了一个单参数函数 只接受回调函数作为参数
    // 这个单参数版本 就叫做 Thunk 函数

    // 任何函数 只要参数有回调函数 就能写成 Thunk 函数的形式 下面是一个简单的 Thunk 函数转换器
    // ES5版本
    var Thunk = function(fn){
        return function (){
            var args = Array.prototype.slice.call(arguments);
            return function (callback){
                args.push(callback);
                return fn.apply(this, args);
            }
        };
    };

    // ES6版本
    const Thunk = function(fn) {
        return function (...args) {
            return function (callback) {
                return fn.call(this, ...args, callback);
            }
        };
    };


    // 使用上面的转换器，生成fs.readFile的 Thunk 函数。

    var readFileThunk = Thunk(fs.readFile);
    readFileThunk(fileA)(callback);



    // 再举一例
    function f(a, cb) {
        cb(a);
    }
    const ft = Thunk(f);

    ft(1)(console.log) // 1

    


</script>